H.265/HEVC在Web視訊播放的實踐
H.265
以下是百度百科對於H.265的介紹: H.265是ITU-T VCEG繼H.264之後所制定的新的視訊編碼標準。H.265標準圍繞著現有的視訊編碼標準H.264,保留原來的某些技術,同時對一些相關的技術加以改進。新技術使用先進的技術用以改善碼流、編碼質量、延時和演算法複雜度之間的關係,達到最優化設定。具體的研究內容包括:提高壓縮效率、提高魯棒性和錯誤恢復能力、減少實時的時延、減少通道獲取時間和隨機接入時延、降低複雜度等。H.264由於演算法優化,可以低於1Mbps的速度實現標清(解析度在1280P720以下)數字影象傳送;H.265則可以實現利用1~2Mbps的傳輸速度傳送720P(解析度1280
相比H.264帶來了很多質的提升,相關對比可以訪問H.265與H.264的差異詳解。總之,H.265, HEVC 作為當前非常火的視訊壓縮方式,相對於大家熟知的 H.264 ,平均可以帶來接近於 50% 的寬度節省。
相關知識
視訊播放器架構
一個典型的現代播放器可以分為三個部分:UI、多媒體引擎和解碼器,架構模型如下圖:
硬解碼支援
隨著 4K 視訊越來越流行,Apple公司的最新的作業系統版本(Mac Hight Sierra和iOS 11)迎來了 HEVC (高效視訊編碼,也稱 H.265) 這一新的行業標準[6]。與現行的 H.264 視訊壓縮標準相比,它的視訊壓縮率最高可提升 50% 之多。使用H.265,在保持視訊畫質不變的情況下,視訊流媒體傳輸效果更好。而在相同位元速率下,能給質量帶來近兩倍的提升。下圖是兩張相同位元速率相同解析度(400kpbs 1080p)的圖片,左邊的採用H.265編碼,右邊的採用H.264編碼。
Web軟解方案
除了硬解碼方案之外,軟體解碼也成為一種有效的選擇,由於H.265視訊的解碼是一個對效能要求很高的CPU密集任務,Web端指令碼語言實現的解碼器的效能很難達到要求。基於此,我們可以通過基於Flash的H.265解碼方案,即通過FlasCC[11]編譯器把C語言編寫的解碼器編譯成swc庫,然後在Flash播放器中用Action Script呼叫swc庫。
另一種方案是基於HTML5的,即通過WebAssembly技術將金山雲自研的高效能解碼器編譯為wasm庫,wasm檔案是以二進位制形式存在的,其中包含平臺無關的虛擬指令(類似彙編指令)。這也是很多移動平臺採用的方案。
相關HTML5技術
下圖是播放器核心主要模組與依賴的背景技術。
Media Source Extensions(簡稱MSE):
提供了實現無外掛且基於 Web 的流媒體的功能。使用MSE API(主要包括:Media Source,Source Buffer等),媒體流能夠通過 JavaScript 建立,並且能通過HTMLMediaElement元素(包括:video和audio元素)進行播放。IE11(win8+)及其他現代瀏覽器都支援。
Streams
標準提供了一套API,來建立和操作流資料,具體地,包括ReadableStream, WritableStream, 以及TransformStream。這允許我們可以增量地處理資料,而不必將所有資料快取到記憶體中統一處理。我們可以採用Fetch API獲取視訊資料,返回的body是一個ReadableStream物件。該物件代表一個數據源,內部維護了一個佇列來記錄尚未被讀取的底層資料來源。可以通過ReadableStream的getReader()介面讀取內部佇列中chunk資料。
Web Workers
讓單執行緒的JavaScript具備了多執行緒程式設計的能力,讓視訊播放器核心可以分離解複用、解碼、渲染、UI操作監聽等任務到不同的執行緒中,並行地處理計算密集型任務和介面顯示等。worker間通訊是通過MessageChannel進行。IE10+及其他現代瀏覽器都支援。
WebAssembly
是Web端的位元組碼技術,它定義了一個通用的、體積緊湊、載入迅捷的二進位制格式為編譯目標,能發揮通用硬體的效能,以更接近原生應用的速度執行。在瀏覽器中對H.265編碼的視訊進行軟體解碼,是一項對效能非常有挑戰的任務,JavaScript等指令碼語言無法勝任此項工作。因此可以將C/C++語言編寫的高效能解碼庫編譯成位元組碼,再通過JavaScript呼叫來執行。目前此項技術在Chrome、Firefox、Safari和Edge瀏覽器的較新版本中都可以使用(如Chrome57+,Firefox 52+)。
H.265 Vs H.264
關於H.265 與 H.264更詳細的差異,可以訪問H.265與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.264/AVC,H.265/HEVC提供了更多不同的工具來降低位元速率,以編碼單位來說,H.264中每個巨集塊(macroblock/MB)大小都是固定的16x16畫素,而H.265的編碼單位可以選擇從最小的8x8到最大的64x64。
同時,H.265的幀內預測模式支援33種方向(H.264只支援8種),並且提供了更好的運動補償處理和向量預測方法。 反覆的質量比較測試已經表明,在相同的圖象質量下,相比於H.264,通過H.265編碼的視訊大小將減少大約39-44%。由於質量控制的測定方法不同,這個資料也會有相應的變化。
支援情況
ios
目前,根據蘋果官網的資料,對於HEVC的支援情況可以用下面的一句話來說明: iOS 11 and macOS High Sierra introduced support for these new, industry-standard media formats。 也即是說,下面的裝置都是支援的。
- iPhone 7 or iPhone 7 Plus or later
- iPad (6th generation)
- iPad Pro (10.5 inch)
- iPad Pro 12.9-inch (2nd generation)
Android
瀏覽器
目前,瀏覽器對於H.265支援的並不是很友好:
實戰
目前,HEVC 的普及速度還沒有那麼快,不過我們還是可以嘗試在 Web 中優雅的播放 H265 視訊。
判斷是否支援播放
要判斷平臺是否支援H.265格式的視訊,可以通過H265 的 mimetype 值來判斷:type="video/mp4; codecs=hevc"。例如:
var supportHEVC = function(video) {
if (typeof video.canPlayType == ‘ function’) {
var playable = elem.canPlayType('video/mp4; codecs="hevc"');
if ((playable.toLowerCase() == 'maybe') || (playable.toLowerCase() == 'probably')) {
return true;
}
}
return false;
};
複製程式碼
如果,使用H.265無法播放視訊,則使用H.264進行播放,所以我們可以通過 source 設定多種格式:
<video controls autoplay>
<source src="your_video.mp4" type="video/mp4; codecs=hevc">
<source src="your_video.webm" type="video/webm; codecs=vp9">
<source src="your_video.mp4" type="video/mp4; codecs=avc1">
</video>
複製程式碼
對於web平臺來說,我們用到的是libde265.js,它是一個通過 JS 來解碼 H.265 視訊的庫,它通過將 視訊的 frame data 轉化為 rgba 畫素,然後繪製到 Canvas 上。下面是使用示例:
<canvas id="canvas"></canvas>
<script src="./libde265.min.j"></script>
<script>
var VIDEO_URL = 'h265-test-640.mp4'
var video = document.getElementById('canvas')
// var fpsWrap = document.querySelector('.hevc-fps')
var status = document.querySelector('.hevc-status')
var playback = function (event) {
// event.preventDefault()
if (player) {
player.stop()
}
player = new libde265.RawPlayer(video)
player.set_status_callback(function (msg, fps) {
player.disable_filters(true)
console.log(msg);
switch (msg) {
case 'loading':
status.innerHTML = 'Loading movie...'
break
case 'initializing':
status.innerHTML = 'Initializing...'
break
case 'playing':
status.innerHTML = 'Playing...'
break
case 'stopped':
status.innerHTML = 'stopped'
break
case 'fps':
// fpsWrap.innerHTML = Number(fps).toFixed(2) + ' fps'
break
default:
status.innerHTML = msg
}
})
player.playback(VIDEO_URL)
}
playback()
</script>
複製程式碼