再也不碎片化學習,快速掌握 H5 直播技術

如今,大多數已工做的前端工做者的學習方式,要麼直接到 Stackoverflow 上搜代碼,要麼直接看看相關博文。這樣是快,可是零零碎碎只是一個一個孤立的知識點而已。有可能一下午都忘記了,惟一可能記住的收藏一下那個文章,而後就完全躺屍了。那有沒有啥更好的辦法能解決呢?html

固然有,第一,有時間,第二,有人指導,第三,找對資料。前端

這其實和看書是同樣的,一本書,最有價值的地方不在它的內容或者做者,而在於它的目錄,是否真正的打動你。若是隻是出現一些模糊而沒有落地技術的目錄的書籍,仍是別再上面浪費時間了。node

因此,本文主要給你們介紹一下當下 HTML5 直播所涵蓋的技術範圍,若是要深度學習每個技術,咱們後續能夠繼續討論。web

直播協議

直播是 16 年搭着短視頻熱火起來的。它的業務場景有不少,有遊戲主播,才藝主播,網上教學,羣體實驗(前段時間,有人直播讓觀衆來炒股)等等。不過,根據技術需求的劃分,還能夠分爲低延遲和高延遲的直播,這裏就主要是協議選擇的問題。算法

如今,經常使用的直播協議有不少種,好比 RTMP,HLS,HTTP-FLV。不過,最經常使用的仍是 HLS 協議,由於支持度高,技術簡單,可是延遲很是嚴重。這對一些對實時性比較高的場景,好比運動賽事直播來講很是蛋疼。這裏,咱們來細分的看一下每一個協議。vim

HLS

HLS 全稱是 HTTP Live Streaming。這是 Apple 提出的直播流協議。(其實,Adobe 公司 FLV 播放器的沒落,蘋果也是幕後黑手之一。) 後端

HLS 由兩部分構成,一個是 .m3u8 文件,一個是 .ts 視頻文件(TS 是視頻文件格式的一種)。整個過程是,瀏覽器會首先去請求 .m3u8 的索引文件,而後解析 m3u8,找出對應的 .ts 文件連接,並開始下載。更加詳細的說明能夠參考這幅圖:api

image.png-93.1kB
image.png-93.1kB

他的使用方式爲:數組

<video controls autoplay>  
    <source src="http://devimages.apple.com/iphone/samples/bipbop/masterplaylist.m3u8" type="application/vnd.apple.mpegurl" /> 
    <p class="warning">Your browser does not support HTML5 video.</p>  
</video>複製代碼

直接能夠將 m3u8 寫進 src 中,而後交由瀏覽器本身去解析。固然,咱們也能夠採起 fetch 來手動解析並獲取相關文件。HLS 詳細版的內容比上面的簡版多了一個 playlist,也能夠叫作 master。在 master 中,會根據網絡段實現設置好不一樣的 m3u8 文件,好比,3G/4G/wifi 網速等。好比,一個 master 文件中爲:瀏覽器

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2855600,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=960x540
live/medium.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=5605600,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720
live/high.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1755600,CODECS="avc1.42001f,mp4a.40.2",RESOLUTION=640x360
live/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=545600,CODECS="avc1.42001e,mp4a.40.2",RESOLUTION=416x234
live/cellular.m3u8複製代碼

你們只要關注 BANDWIDTH(帶寬)字段,其餘的看一下字段內容大體就清楚了。假如這裏選擇 high.m3u8 文件,那麼,裏面內容爲:

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:26
#EXTINF:9.901,
http://media.example.com/wifi/segment26.ts
#EXTINF:9.901,
http://media.example.com/wifi/segment27.ts
#EXTINF:9.501,
http://media.example.com/wifi/segment28.ts複製代碼

注意,其中以 ts 結尾的連接就是咱們在直播中真正須要播放的視頻文件。該第二級的 m3u8 文件也能夠叫作 media 文件。該文件,其實有三種類型:

  • live playlist: 動態列表。顧名思義,該列表是動態變化的,裏面的 ts 文件會實時更新,而且過時的 ts 索引會被刪除。默認,狀況下都是使用動態列表。
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:26
#EXTINF:9.901,
http://media.example.com/wifi/segment26.ts
#EXTINF:9.901,
http://media.example.com/wifi/segment27.ts
#EXTINF:9.501,
http://media.example.com/wifi/segment28.ts複製代碼
  • event playlist: 靜態列表。它和動態列表主要區別就是,原來的 ts 文件索引不會被刪除,該列表是不斷更新,並且文件大小會逐漸增大。它會在文件中,直接添加 #EXT-X-PLAYLIST-TYPE:EVENT 做爲標識。
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:EVENT
#EXTINF:9.9001,
http://media.example.com/wifi/segment0.ts
#EXTINF:9.9001,
http://media.example.com/wifi/segment1.ts
#EXTINF:9.9001,
http://media.example.com/wifi/segment2.ts複製代碼
  • VOD playlist: 全量列表。它就是將全部的 ts 文件都列在 list 當中。若是,使用該列表,就和播放一整個視頻沒有啥區別了。它是使用 #EXT-X-ENDLIST 表示文件結尾。
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.9001,
http://media.example.com/wifi/segment0.ts
#EXTINF:9.9001,
http://media.example.com/wifi/segment1.ts
#EXTINF:9.9001,
http://media.example.com/wifi/segment2.ts
#EXT-X-ENDLIST複製代碼

裏面相關字段解釋能夠參考: Apple HLS

HLS 缺陷

HLS 啥都好,就是延遲性太大了,估計蘋果一開始設計的時候,並不在意它的延時性。HLS 中的延時包括:

  • TCP 握手
  • m3u8 文件下載
  • m3u8 文件下全部 ts 文件下載

這裏,咱們先假設每一個 ts 文件播放時長爲 5s,每一個 m3u8 最多可攜帶的 ts 文件數爲 3~8。那麼最大的延遲則爲 40s。注意,只有當一個 m3u8 文件下全部的 ts 文件下載完後,才能開始播放。這裏還不包括 TCP 握手,DNS 解析,m3u8 文件下載。因此,HLS 總的延時是很是使人絕望的。那解決辦法有嗎? 有,很簡單,要麼減小每一個 ts 文件播放時長,要麼減小 m3u8 的中包含 ts 的數量。若是超過平衡點,那麼每次請求新的 m3u8 文件時,都會加上必定的延時,因此,這裏須要根據業務指定合適的策略。固然,如今因爲 mediaSource 的普及,自定義一個播放器也沒有多大的難度,這樣就能夠保證直播延遲性的同時,完成直播的順利進行。

RTMP

RTMP 全稱爲:Real-Time Messaging Protocol。它是基於 FLV 格式進行開發的,因此,第一反應就是,臥槽,又不能用了!!!

是的,在如今設備中,因爲 FLV 的不支持,基本上 RTMP 協議在 Web 中,根本用不到。不過,因爲 MSE(MediaSource Extensions)的出現,在 Web 上直接接入 RTMP 也不是不可能的。基本思路是根據 WebSocket 直接創建長鏈接進行數據的交流和監聽。這裏,咱們就先不細說了。咱們主要目的是講概念,講框架。RTMP 協議根據不一樣的套層,也能夠分爲:

  • 純 RTMP: 直接經過 TCP 鏈接,端口爲 1935
  • RTMPS: RTMP + TLS/SSL,用於安全性的交流。
  • RTMPE: RTMP + encryption。在 RTMP 原始協議上使用,Adobe 自身的加密方法
  • RTMPT: RTMP + HTTP。使用 HTTP 的方式來包裹 RTMP 流,這樣能直接經過防火牆。不過,延遲性比較大。
  • RTMFP: RMPT + UDP。該協議經常用於 P2P 的場景中,針對延時有變態的要求。

RTMP 內部是藉由 TCP 長鏈接協議傳輸相關數據,因此,它的延時性很是低。而且,該協議靈活性很是好(因此,也很複雜),它能夠根據 message stream ID 傳輸數據,也能夠根據 chunk stream ID 傳遞數據。二者均可以起到流的劃分做用。流的內容也主要分爲:視頻,音頻,相關協議包等。

詳細傳輸過程如圖:

image.png-29.3kB
image.png-29.3kB

若是後期要使用到 RTMP 協議,能夠直接參考

HTTP-FLV

該協議和 RTMP 比起來其實差異不大,只是落地部分有些不一樣:

RTMP 是直接將流的傳輸架在 RTMP 協議之上,而 HTTP-FLV 是在 RTMP 和客戶端之間套了一層轉碼的過程,即:

image.png-8.2kB
image.png-8.2kB

因爲,每一個 FLV 文件是經過 HTTP 的方式獲取的,因此,它經過抓包得出的協議頭須要使用 chunked 編碼。

Content-Type:video/x-flv
Expires:Fri, 10 Feb 2017 05:24:03 GMT
Pragma:no-cache
Transfer-Encoding:chunked複製代碼

它用起來比較方便,不事後端實現的難度和直接使用 RTMP 來講仍是比較大的。

上面簡單介紹了一下三種協議,具體選擇哪一種協議,仍是須要和具體的業務進行強相關,不然的話吃虧的仍是本身(本身挖坑)。。。

這裏,簡單的作個對比

協議對比

協議 優點 缺陷 延遲性
HLS 支持性廣 延時巨高 10s 以上
RTMP 延時性好,靈活 量大的話,負載較高 1s 以上
HTTP-FLV 延時性好,遊戲直播經常使用 只能在手機 APP 播放 2s 以上

前端音視頻流

因爲各大瀏覽器的對 FLV 的圍追堵截,致使 FLV 在瀏覽器的生存情況堪憂,可是,FLV 憑藉其格式簡單,處理效率高的特色,使各大視頻後臺的開發者都捨不得啓用,若是一旦更改的話,就須要對現有視頻進行轉碼,好比變爲 MP4,這樣不只在播放,並且在流處理來講都有點重的讓人沒法接受。而 MSE 的出現,完全解決了這個尷尬點,可以讓前端可以自定義來實現一個 Web 播放器,確實完美。(不過,蘋果老大爺以爲沒這必要,因此,在 IOS 上沒法實現。)

MSE

MSE 全稱就是 Media Source Extensions。它是一套處理視頻流技術的簡稱,裏面包括了一系列 API:Media SourceSource Buffer 等。在沒有 MSE 出現以前,前端對 video 的操做,僅僅侷限在對視頻文件的操做,而並不能對視頻流作任何相關的操做。如今 MSE 提供了一系列的接口,使開發者能夠直接提供 media stream。

咱們來看一下 MSE 是如何完成基本流的處理的。

var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.log("The Media Source Extensions API is not supported.")
}

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp9"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  fetch(videoUrl)
    .then(function(response) {
      return response.arrayBuffer();
    })
    .then(function(arrayBuffer) {
      sourceBuffer.addEventListener('updateend', function(e) {
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
          mediaSource.endOfStream();
        }
      });
      sourceBuffer.appendBuffer(arrayBuffer);
    });
}複製代碼

上面的代碼完成了相關的獲取流和處理流的兩個部分。其中,主要利用的是 MS 和 Source Buffer 來完成的。接下來,咱們來具體涉及一下詳細內容:

MediaSource

MS(MediaSource) 只是一系列視頻流的管理工具,它能夠將音視頻流完整的暴露給 Web 開發者來進行相關的操做和處理。因此,它自己不會形成過分的複雜性。

MS 整個只掛載了 4 個屬性,3 個方法和 1 個靜態測試方法。有:

4 個屬性:

  • sourceBuffers: 得到當前建立出來的 SourceBuffer
  • activeSourceBuffers: 得到當前正處於激活狀態的 SourceBuffer
  • readyState: 返回當前 MS 的狀態,好比: closed,open,ended.
  • duration: 設置當前 MS 的播放時長。

3 個方法:

  • addSourceBuffer(): 根據給定的 MIME 建立指定類型的 SourceBuffer
  • removeSourceBuffer(): 將 MS 上指定的 SourceBuffer 移除。
  • endOfStream(): 直接終止該流

1 個靜態測試方法:

  • isTypeSupported(): 主要用來判斷指定的音頻的 MIME 是否支持。

最基本的就是使用 addSourceBuffer 該方法來得到指定的 SourceBuffer。

var sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');複製代碼

Source Buffer

一旦利用 MS 建立好 SourceBuffer 以後,後續的工做就是將額外得到的流放進 Buffer 裏面進行播放便可。因此,SourceBuffer 提供兩個最基本的操做 appendBufferremove。以後,咱們就能夠經過 appendBuffer 直接將 ArrayBuffer 放進去便可。

其中,SourceBuffer 還提供了一個應急的方法 abort() 若是該流發生問題的話能夠直接將指定的流給廢棄掉。

因此,整個流程圖爲:

image.png-16kB
image.png-16kB

音視頻的 ArrayBuffer 經過 MediaSource 和 SourceBuffer 的處理直接將 <audio> && <video> 接入。而後,就能夠實現正常播放的效果。

固然,上面介紹的僅僅只是一些概念,若是要實際進行編碼的話,還得繼續深刻下去學習。有興趣的同窗,能夠繼續深刻了解,個人另一篇博客:全面進階 H5 直播

固然,若是後期有機會,能夠繼續來實現如下如何進行實際的編碼。本文,主要是給你們介紹直播所需的必要技術和知識點,只有完備以後,咱們才能沒有障礙的完成實際編碼的介紹。

流的處理

上面咱們已經講解了在直播中,咱們怎樣經過 MSE 接觸到實際播放的流,那麼,接下來,咱們就要開始腳踏實地的瞭解具體的流的操做處理。由於,視頻流格式解協議中,最常涉及的就是拼包,修改字段,切包等操做。

在正式介紹以前,咱們須要先了解一下關於流的一些具體概念:

二進制

二進制沒啥說的就是 比特流。可是,在 Web 中,有幾個簡寫的進制方式:二進制,八進制,十六進制。

  • 二進制(binary):使用 0b 字面上表示二進制。每一位表明 1bit(2^1)
  • 八進制(octet): 使用 0o 字面上表示八進制。每一位表明 3bit(2^3)
  • 十六進制(hexadecimal): 使用 0x 字面上表示十六進制。每一位表明 4bit(2^4)

上面說的每一位表明着,實際簡寫的位數,好比 0xff31; 這個就表明 2B 的長度。

位運算

位運算在處理流操做中,是很是重要的,不過因爲前端 Buffer 提供的操做集合很少,因此,有些輪子咱們還得須要本身構造一下。

這裏,我就不細緻介紹,在 Web 中經常使用的位運算符有:

  • &
  • |
  • ~
  • ^
  • <<
  • >

  • >

詳細介紹能夠參考 Web 位運算

整個優先級爲:

~ >> << >>> & ^ |複製代碼

字節序

字節序說白了就是 bit 放置的順序,由於歷史遺漏緣由,字節的放置有兩種順序:

  • 大字節序(BigEndian): 將數據從大到小放置,認爲第一個字節是最高位(正常思惟)。
  • 小字節序(LittleEndian):將數據從小到達防止,認爲第一個字節是最低位。

這個概念在咱們後面寫入過程當中,常常用到。固然,咱們如何瞭解到某臺電腦使用的是大字節仍是小字節呢?(其實大部分都是小字節)。可使用 IIFE 進行簡單的判斷:

const LE = (function () {
    let buf = new ArrayBuffer(2);
    (new DataView(buf)).setInt16(0, 256, true);  // little-endian write
    return (new Int16Array(buf))[0] === 256;  // platform-spec read, if equal then LE
})();複製代碼

而在前端,咱們歸根結底的就是操做 ArrayBuffer。它也是咱們直接和 Buffer 交流的通道。

ArrayBuffer

AB(ArrayBuffer) 不是像 NodeJS 的 Buffer 對象同樣是一個純粹的集合流處理的工具。它只是一個流的容器,這也是底層 V8 實現的內容。基本用法就是給實例化一個固定的內存區:

new ArrayBuffer(length)複製代碼

建立指定的 length Byte 內存大小。此時,它裏面只是空的內存,這時候你須要借用其餘兩個對象 TypedArrayDataView 來幫助你完成寫入和修改的操做。不過,AB 提供了一個很是重要的方法:slice()

slice() 和 Array 對象上的 slice 方法同樣也是將數組中的一部分新建立一個副本返回。這個方法爲啥有用呢?

由於,經過 TypedArrayDataView 建立的對象底層的 AB 都是不能改變的,因此,若是你想對一個 Buffer 進行不一樣的操做,好比,對 AB 的 4-8B 所有置 0,而且後面又置爲 1。若是你想保留二者的話,就須要手動建立一個副本才行,這就須要用到 slice 方法了。

AB 具體的屬性和方法我這裏就很少說了,有興趣的同窗能夠參考 MDN ArrayBuffer

接下來,咱們就來看一下和 AB 實際對接最緊密的兩個對象 TypedArrayDataView

TypedArray

TA(TypedArray) 是一套 ArrayBuffer 處理的集合。怎麼說呢?它裏面還能夠細分爲

Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();複製代碼

爲何會有這麼多呢?

由於 TA 是將 Buffer 根據指定長度分隔爲指定大小的數組。好比:

var buf = new Uint8Array(arrayBuffer);

buf[0]
buf[1]
...複製代碼

像這樣具體經過 index 來獲取指定的 Buffer 中的比特值。好比像上面 Uint8Array 每一位就是 1B。

出來分隔的長度不一樣,剩下的內容,基本上就能夠用 TA 來進行總體歸納。實際上,你們也能夠把它理解爲 Array 便可。爲啥呢?你能夠看一下它有哪些方法後,就完全明白了:

reverse()
set()
slice()
some()
sort()
subarray()
...複製代碼

不過,因爲兼容性的緣由,對於某些方法來講,咱們須要加上相關的 polyfill 才行。不過,這也不影響咱們的研究性學習,而且,由於 MSE 是針對現代手機瀏覽器開發的,因此,咱們在作 Web 播放器的時候,也並不須要過分關注瀏覽器的兼容。

TypedArray 最經常使用的操做方式,是直接根據 index 進行相關的寫入操做:

buf[0] = fmt << 6 | 1;
buf[1] = chunkID % 256 - 64;
buf[2] = Math.floor(chunkID / 256);複製代碼

須要注意在 TypedArray 中的字節序,是根據平臺默認的字節序來讀取 Buffer 的,好比 UintArray32()。不過,大部分平臺默認都是 little-endian 來進行讀取。

DataView

DV(DataView) 和 TypedArray 很相似,也是用來修改底層的 Buffer 的。說白了,它倆就是 NodeJS Buffer 的兩部分,可能因爲某些緣由,將二者給分開建立。DataView 提供的 API 很簡單,就是一些 get/set 之類的方法。基本用法爲:

new DataView(Arraybuffer [, byteOffset [, byteLength]])複製代碼

注意,前面那個參數只能是 ArrayBuffer ,你不能把 TypedArray 也給我算進去,否則的話...你能夠試試。

一樣須要提醒的是 DataView 的修改是和對象同樣的,是進行引用類型的修改,即,若是對一個 Buffer 建立多個 DataView 那麼,屢次修改只會在一個 Buffer 顯現出來。

DV 最大的用處就可能夠很方便的寫入不一樣字節序的值,這相比於使用 TypedArray 來作 swap()(交換) 是很方便的事。固然,字節序相關也只能是大於 8 bit 以上的操做纔有效。

這裏以 setUInt32 爲例子,其基本格式爲:

setInt32(byteOffset, value [, littleEndian])複製代碼

其中,littleEndian 是 boolean 值,用來表示寫入字節序的方式,默認是使用大字節序。參考

It is big-endian by default and can be set to little-endian in the getter/setter methods.

因此,若是你想使用小字節序的話,則須要手動傳入 true 才行!

好比:

let view = new DataView(buffer);
view.setUint32(0, arr[0] || 1, BE);


// 使用 TypedArray 手動構造 swap
buf = new Uint8Array(11);
buf[3] = byteLength >>> 16 & 0xFF;
buf[4] = byteLength >>> 8 & 0xFF;
buf[5] = byteLength & 0xFF;複製代碼

固然,若是你以爲不放心,能夠直接使用,一個 IIFE 進行相關判斷:

const LE = (function () {
            let buf = new ArrayBuffer(2);
            (new DataView(buf)).setInt16(0, 256, true); // little-endian write
            return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE
        })();複製代碼

上面是前端 Buffer 的部分,爲了讓你們更好的瞭解到 JS 開發工做者從前端到後端操做 Buffer 的區別,這裏一併提一下在 NodeJS 中如何處理 Buffer。

Node Buffer

Node Buffer 實際上纔是前端最好用的 Buffer 操做,由於它是整合的 ArrayBuffer , TypedArray ,Dataview 一塊兒的一個集合,該對象上掛載了全部處理的方式。詳情能夠參考一下:Node Buffer

他能夠直接經過 allocfrom 方法來直接建立指定的大小的 Buffer。之前那種經過 new Buffer 的方法官方已經不推薦使用了,具體緣由能夠 stackoverflow 搜一搜,這裏我就很少說了。這裏想特別提醒的是,NodeJS 已經能夠和前端的 ArrayBuffer 直接轉換了。經過 from 方法,能夠直接將 ArrayBuffer 轉換爲 NodeJS 的 Buffer。

格式爲:

Buffer.from(arrayBuffer[, byteOffset[, length]])複製代碼

參考 NodeJS 提供的 demo:

const arr = new Uint16Array(2);

arr[0] = 5000;
arr[1] = 4000;

// 共享 arr 的緩存
const buf = Buffer.from(arr.buffer);

// 打印結果: <Buffer 88 13 a0 0f>
console.log(buf);

// 直接改變原始的 Buffer 值
arr[1] = 6000;

// 打印: <Buffer 88 13 70 17>
console.log(buf);複製代碼

在 Node Buffer 對象上,還掛載了好比:

buf.readInt16BE(offset[, noAssert])
buf.readInt16LE(offset[, noAssert])
buf.readInt32BE(offset[, noAssert])
buf.readInt32LE(offset[, noAssert])複製代碼

有點不一樣的是,它是直接根據名字的不一樣而決定使用哪一種字節序。

  • BE 表明 BigEndian
  • LE 表明 LittleEndian

以後,咱們就可使用指定的方法進行寫入和讀取操做。

const buf = Buffer.from([0, 5]);

// Prints: 5
console.log(buf.readInt16BE());

// Prints: 1280
console.log(buf.readInt16LE());複製代碼

在實際使用中,咱們通常對照着 Node 官方文檔使用便可,裏面文檔很詳盡。

音視頻基本概念

爲了你們可以在學習中減小必定的不適感,這裏先給你們介紹一下音視頻的基本概念,以防止之後別人在吹逼,你能夠在旁邊微微一笑。首先,基本的就是視頻格式和視頻壓縮格式。

視頻格式應該不用多說,就是咱們一般所說的 .mp4,.flv,.ogv,.webm 等。簡單來講,它其實就是一個盒子,用來將實際的視頻流以必定的順序放入,確保播放的有序和完整性。

視頻壓縮格式和視頻格式具體的區別就是,它是將原始的視頻碼流變爲可用的數字編碼。由於,原始的視頻流很是大,打個比方就是,你直接使用手機錄音,你會發現你幾分鐘的音頻會比市面上出現的 MP3 音頻大小大不少,這就是壓縮格式起的主要做用。具體流程圖以下:

image.png-37.1kB
image.png-37.1kB

首先,由原始數碼設備提供相關的數字信號流,而後經由視頻壓縮算法,大幅度的減小流的大小,而後交給視頻盒子,打上相應的 dtspts 字段,最終生成可用的視頻文件。經常使用的視頻格式和壓縮格式以下:

image.png-136.4kB
image.png-136.4kB

視頻格式主要是參考 ISO 提供的格式文件進行學習,而後參照進行編解碼便可。

這裏,主要想介紹一下壓縮算法,由於這個在你實際解碼用理解相關概念很重要。

首先來看一下,什麼叫作視頻編碼。

視頻編碼

視頻實際上就是一幀一幀的圖片,拼接起來進行播放而已。而圖片自己也能夠進行相關的壓縮,好比去除重複像素,合併像素塊等等。不過,還有另一種壓縮方法就是,運動估計和運動補償壓縮,由於相鄰圖片必定會有一大塊是類似的,因此,爲了解決這個問題,能夠在不一樣圖片之間進行去重。

因此,總的來講,經常使用的編碼方式分爲三種:

  • 變換編碼:消除圖像的幀內冗餘
  • 運動估計和運動補償:消除幀間冗餘
  • 熵編碼:提升壓縮效率
變換編碼

這裏就涉及到圖像學裏面的兩個概念:空域和頻域。空域就是咱們物理的圖片,頻域就是將物理圖片根據其顏色值等映射爲數字大小。而變換編碼的目的是利用頻域實現去相關和能量集中。經常使用的正交變換有離散傅里葉變換,離散餘弦變換等等。

熵編碼

熵編碼主要是針對碼節長度優化實現的。原理是針對信源中出現機率大的符號賦予短碼,對於機率小的符號賦予長碼,而後總的來講實現平均碼長的最小值。編碼方式(可變字長編碼)有:霍夫曼編碼、算術編碼、遊程編碼等。

運動估計和運動補償

上面那兩種辦法主要是爲了解決圖像內的關聯性。另外,視頻壓縮還存在時間上的關聯性。例如,針對一些視頻變化,背景圖不變而只是圖片中部分物體的移動,針對這種方式,能夠只對相鄰視頻幀中變化的部分進行編碼。

接下來,再來進行說明一下,運動估計和運動補償壓縮相關的,I,B,P 幀。

I,B,P 幀

I,B,P 其實是從運動補償中引出來的,這裏爲了後面的方便先介紹一下。

  • I 幀(I-frame): 學名叫作: Intra-coded picture。也能夠叫作獨立幀。該幀是編碼器隨機挑選的參考圖像,換句話說,一個 I 幀自己就是一個靜態圖像。它是做爲 B,P 幀的參考點。對於它的壓縮,只能使用變化編碼 這兩種方式進行幀內壓縮。因此,它的運動學補償基本沒有。
  • P 幀(P‑frame): 又叫作 Predicted picture--前向預測幀。即,他會根據前面一張圖像,來進行圖片間的動態壓縮,它的壓縮率和 I 幀比起來要高一些。
  • B 幀(B‑frame): 又叫作 Bi-predictive picture-- 雙向預測。它比 P 幀來講,還多了後一張圖像的預測,因此它的壓縮率更高。

能夠參考一下雷博士的圖:

image.png-54kB
image.png-54kB

不過,這樣理解 OK,若是一旦涉及實際編碼的話,那麼就不是那麼一回事了。設想一下,視頻中的 IBP 三幀,頗有可能會遇到 I B B P 的情形。這樣其實也還好,不過在對數據解碼的時候,就會遇到一個問題,B 幀是相對於先後兩幀的,可是隻有前一幀是固定幀,後面又相對於前面,即,B 幀只能相對於 I/P。可是,這時候 P 幀又尚未被解析。因此,爲了解決這個問題,在解碼的時候,就須要將他們換一個位置,即 I P B B。這樣就能夠保證解碼的正確性。

那怎麼進行保證呢?這就須要 DTS 和 PTS 來完成。這兩個是咱們在進行視頻幀編解碼中最終要的兩個屬性(固然還有一個 CTS)。

解釋一下:

  • pts(presentation time stamps):顯示時間戳,顯示器從接受到解碼到顯示的時間。
  • dts(decoder timestamps): 解碼時間戳。也表示該 sample 在整個流中的順序

因此視頻幀的順序簡單的來表示一下就是:

PTS: 1 4 2 3
   DTS: 1 2 3 4
Stream: I P B B複製代碼

能夠看到,咱們使用 DTS 來解碼,PTS 來進行播放。OK,關於 Web 直播的大體基本點差很少就介紹完了。後面若是還有機會,咱們能夠來進行一下 音視頻解碼的實戰操練。

更多內容,能夠關注個人公衆號:前端小吉米

相關文章
相關標籤/搜索