1. 程式人生 > 實用技巧 >web Video播放器學習

web Video播放器學習

from奇舞週刊

https://mp.weixin.qq.com/s/thnhhbw2ieFywCFSCHXyGQ

Web 開發者們一直以來想在 Web 中使用音訊和視訊,但早些時候,傳統的 Web 技術不能夠在 Web 中嵌入音訊和視訊,所以一些像 Flash、Silverlight 的專利技術在處理這些內容上變得很受歡迎。

這些技術能夠正常的工作,但是卻有著一系列的問題,包括無法很好的支援 HTML/CSS 特性、安全問題,以及可行性問題。

幸運的是,當 HTML5 標準公佈後,其中包含許多的新特性,包括<video><audio>標籤,以及一些 JavaScript APIs 用於對其進行控制。隨著通訊技術和網路技術的不斷髮展,目前音視訊已經成為大家生活中不可或缺的一部分。此外,伴隨著 5G 技術的慢慢普及,實時音視訊領域還會有更大的想象空間。

接下來本文將從八個方面入手,全方位帶你一起探索前端 Video 播放器和主流的流媒體技術。閱讀完本文後,你將瞭解以下內容:

  • 為什麼一些網頁中的 Video 元素,其視訊源地址是採用 Blob URL 的形式;
  • 什麼是 HTTP Range 請求及流媒體技術相關概念;
  • 瞭解 HLS、DASH 的概念、自適應位元率流技術及流媒體加密技術;
  • 瞭解 FLV 檔案結構、flv.js 的功能特性與使用限制及內部的工作原理;
  • 瞭解 MSE(Media Source Extensions)API 及相關的使用;
  • 瞭解視訊播放器的原理、多媒體封裝格式及 MP4 與 Fragmented MP4 封裝格式的區別;

在最後的「阿寶哥有話說」環節,阿寶哥將介紹如何實現播放器截圖、如何基於截圖生成 GIF、如何使用 Canvas 播放視訊及如何實現色度鍵控等功能。

一、傳統的播放模式

大多數 Web 開發者對<video>都不會陌生,在以下 HTML 片段中,我們聲明瞭一個<video>元素並設定相關的屬性,然後通過<source>標籤設定視訊源和視訊格式:

<videoid="mse"autoplay=trueplaysinlinecontrols="controls">
<sourcesrc="https://h5player.bytedance.com/video/mp4/xgplayer-demo-720p.mp4"type="video/mp4">
你的瀏覽器不支援Video標籤
</video>

上述程式碼在瀏覽器渲染之後,在頁面中會顯示一個 Video 視訊播放器,具體如下圖所示:


(圖片來源:https://h5player.bytedance.com/examples/)

通過 Chrome 開發者工具,我們可以知道當播放「xgplayer-demo-720p.mp4」視訊檔案時,發了 3 個 HTTP 請求:


此外,從圖中可以清楚地看到,頭兩個 HTTP 請求響應的狀態碼是「206」。這裡我們來分析第一個 HTTP 請求的請求頭和響應頭:


在上面的請求頭中,有一個range: bytes=0-首部資訊,該資訊用於檢測服務端是否支援 Range 請求。如果在響應中存在Accept-Ranges首部(並且它的值不為 “none”),那麼表示該伺服器支援範圍請求。

在上面的響應頭中,Accept-Ranges: bytes表示界定範圍的單位是bytes。這裡Content-Length也是有效資訊,因為它提供了要下載的視訊的完整大小。

1.1 從伺服器端請求特定的範圍

假如伺服器支援範圍請求的話,你可以使用 Range 首部來生成該類請求。該首部指示伺服器應該返回檔案的哪一或哪幾部分。

1.1.1 單一範圍

我們可以請求資源的某一部分。這裡我們使用 Visual Studio Code 中的 REST Client 擴充套件來進行測試,在這個例子中,我們使用 Range 首部來請求 www.example.com 首頁的前 1024 個位元組。


對於使用 REST Client 發起的「單一範圍請求」,伺服器端會返回狀態碼為「206 Partial Content」的響應。而響應頭中的「Content-Length」首部現在用來表示先前請求範圍的大小(而不是整個檔案的大小)。「Content-Range」響應首部則表示這一部分內容在整個資源中所處的位置。

1.1.2 多重範圍

Range 頭部也支援一次請求文件的多個部分。請求範圍用一個逗號分隔開。比如:

$curlhttp://www.example.com-i-H"Range:bytes=0-50,100-150"

對於該請求會返回以下響應資訊:


因為我們是請求文件的多個部分,所以每個部分都會擁有獨立的「Content-Type」「Content-Range」資訊,並且使用 boundary 引數對響應體進行劃分。

1.1.3 條件式範圍請求

當重新開始請求更多資源片段的時候,必須確保自從上一個片段被接收之後該資源沒有進行過修改。

「If-Range」請求首部可以用來生成條件式範圍請求:假如條件滿足的話,條件請求就會生效,伺服器會返回狀態碼為 206 Partial 的響應,以及相應的訊息主體。假如條件未能得到滿足,那麼就會返回狀態碼為「200 OK」的響應,同時返回整個資源。該首部可以與「Last-Modified」驗證器或者「ETag」一起使用,但是二者不能同時使用。

1.1.4 範圍請求的響應

與範圍請求相關的有三種狀態:

  • 在請求成功的情況下,伺服器會返回「206 Partial Content」狀態碼。
  • 在請求的範圍越界的情況下(範圍值超過了資源的大小),伺服器會返回「416 Requested Range Not Satisfiable」(請求的範圍無法滿足) 狀態碼。
  • 在不支援範圍請求的情況下,伺服器會返回「200 OK」狀態碼。

剩餘的兩個請求,阿寶哥就不再詳細分析了。感興趣的小夥伴,可以使用 Chrome 開發者工具檢視一下具體的請求報文。

通過第 3 個請求,我們可以知道整個視訊的大小大約為 7.9 MB。若播放的視訊檔案太大或出現網路不穩定,則會導致播放時,需要等待較長的時間,這嚴重降低了使用者體驗。

那麼如何解決這個問題呢?要解決該問題我們可以使用流媒體技術,接下來我們來介紹流媒體。

二、流媒體

流媒體是指將一連串的媒體資料壓縮後,經過網上分段傳送資料,在網上即時傳輸影音以供觀賞的一種技術與過程,此技術使得資料包得以像流水一樣傳送;如果不使用此技術,就必須在使用前下載整個媒體檔案。

流媒體實際指的是一種新的媒體傳送方式,有聲音流、視訊流、文字流、影象流、動畫流等,而非一種新的媒體。流媒體最主要的技術特徵就是流式傳輸,它使得資料可以像流水一樣傳輸。流式傳輸是指通過網路傳送媒體技術的總稱。實現流式傳輸主要有兩種方式:順序流式傳輸(Progressive Streaming)和實時流式傳輸(Real Time Streaming)。

目前網路上常見的流媒體協議:

通過上表可知,不同的協議有著不同的優缺點。在實際使用過程中,我們通常會在平臺相容的條件下選用最優的流媒體傳輸協議。比如,在瀏覽器裡做直播,選用 HTTP-FLV 協議是不錯的,效能優於 RTMP+Flash,延遲可以做到和 RTMP+Flash 一樣甚至更好。

而由於 HLS 延遲較大,一般只適合視訊點播的場景,但由於它在移動端擁有較好的相容性,所以在接受高延遲的條件下,也是可以應用在直播場景。

講到這裡相信有些小夥伴會好奇,對於 Video 元素來說使用流媒體技術之後與傳統的播放模式有什麼直觀的區別。下面阿寶哥以常見的 HLS 流媒體協議為例,來簡單對比一下它們之間的區別。

通過觀察上圖,我們可以很明顯地看到,當使用 HLS 流媒體網路傳輸協議時,<video>元素src屬性使用的是blob://協議。講到該協議,我們就不得不聊一下 Blob 與 Blob URL。

2.1 Blob

Blob(Binary Large Object)表示二進位制型別的大物件。在資料庫管理系統中,將二進位制資料儲存為一個單一個體的集合。Blob 通常是影像、聲音或多媒體檔案。「在 JavaScript 中 Blob 型別的物件表示不可變的類似檔案物件的原始資料。」

Blob由一個可選的字串type(通常是 MIME 型別)和blobParts組成:

MIME(Multipurpose Internet Mail Extensions)多用途網際網路郵件擴充套件型別,是設定某種副檔名的檔案用一種應用程式來開啟的方式型別,當該副檔名檔案被訪問的時候,瀏覽器會自動使用指定應用程式來開啟。多用於指定一些客戶端自定義的檔名,以及一些媒體檔案開啟方式。

常見的 MIME 型別有:超文字標記語言文字 .html text/html、PNG影象 .png image/png、普通文字 .txt text/plain 等。

為了更直觀的感受 Blob 物件,我們先來使用 Blob 建構函式,建立一個 myBlob 物件,具體如下圖所示:

如你所見,myBlob 物件含有兩個屬性:size 和 type。其中size屬性用於表示資料的大小(以位元組為單位),type是 MIME 型別的字串。Blob 表示的不一定是 JavaScript 原生格式的資料。比如File介面基於Blob,繼承了 blob 的功能並將其擴充套件使其支援使用者系統上的檔案。

2.2 Blob URL/Object URL

Blob URL/Object URL 是一種偽協議,允許 Blob 和 File 物件用作影象,下載二進位制資料鏈接等的 URL 源。在瀏覽器中,我們使用URL.createObjectURL方法來建立 Blob URL,該方法接收一個Blob物件,併為其建立一個唯一的 URL,其形式為blob:<origin>/<uuid>,對應的示例如下:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

瀏覽器內部為每個通過URL.createObjectURL生成的 URL 儲存了一個 URL → Blob 對映。因此,此類 URL 較短,但可以訪問Blob。生成的 URL 僅在當前文件開啟的狀態下才有效。但如果你訪問的 Blob URL 不再存在,則會從瀏覽器中收到 404 錯誤。

上述的 Blob URL 看似很不錯,但實際上它也有副作用。雖然儲存了 URL → Blob 的對映,但 Blob 本身仍駐留在記憶體中,瀏覽器無法釋放它。對映在文件解除安裝時自動清除,因此 Blob 物件隨後被釋放。但是,如果應用程式壽命很長,那不會很快發生。因此,如果我們建立一個 Blob URL,即使不再需要該 Blob,它也會存在記憶體中。

針對這個問題,我們可以呼叫URL.revokeObjectURL(url)方法,從內部對映中刪除引用,從而允許刪除 Blob(如果沒有其他引用),並釋放記憶體。

2.3 Blob vs ArrayBuffer

其實在前端除了「Blob 物件」之外,你還可能會遇到「ArrayBuffer 物件」。它用於表示通用的,固定長度的原始二進位制資料緩衝區。你不能直接操縱 ArrayBuffer 的內容,而是需要建立一個 TypedArray 物件或 DataView 物件,該物件以特定格式表示緩衝區,並使用該物件讀取和寫入緩衝區的內容。

Blob 物件與 ArrayBuffer 物件擁有各自的特點,它們之間的區別如下:

  • 除非你需要使用 ArrayBuffer 提供的寫入/編輯的能力,否則 Blob 格式可能是最好的。
  • Blob 物件是不可變的,而 ArrayBuffer 是可以通過 TypedArrays 或 DataView 來操作。
  • ArrayBuffer 是存在記憶體中的,可以直接操作。而 Blob 可以位於磁碟、快取記憶體記憶體和其他不可用的位置。
  • 雖然 Blob 可以直接作為引數傳遞給其他函式,比如window.URL.createObjectURL()。但是,你可能仍需要 FileReader 之類的 File API 才能與 Blob 一起使用。
  • Blob 與 ArrayBuffer 物件之間是可以相互轉化的:
    • 使用 FileReader 的readAsArrayBuffer()方法,可以把 Blob 物件轉換為 ArrayBuffer 物件;
    • 使用 Blob 建構函式,如new Blob([new Uint8Array(data]);,可以把 ArrayBuffer 物件轉換為 Blob 物件。

在前端 AJAX 場景下,除了常見的 JSON 格式之外,我們也可能會用到 Blob 或 ArrayBuffer 物件:

functionGET(url,callback){
letxhr=newXMLHttpRequest();
xhr.open('GET',url,true);
xhr.responseType='arraybuffer';//orxhr.responseType="blob";
xhr.send();

xhr.onload=function(e){
if(xhr.status!=200){
alert("Unexpectedstatuscode"+xhr.status+"for"+url);
returnfalse;
}
callback(newUint8Array(xhr.response));//ornewBlob([xhr.response]);
};
}

在以上示例中,通過為 xhr.responseType 設定不同的資料型別,我們就可以根據實際需要獲取對應型別的資料了。介紹完上述內容,下面我們先來介紹目前應用比較廣泛的 HLS 流媒體傳輸協議。

三、HLS

3.1 HLS 簡介

HTTP Live Streaming(縮寫是 HLS)是由蘋果公司提出基於 HTTP 的流媒體網路傳輸協議,它是蘋果公司 QuickTime X 和 iPhone 軟體系統的一部分。它的工作原理是把整個流分成一個個小的基於 HTTP 的檔案來下載,每次只下載一些。當媒體流正在播放時,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應不同的資料速率。

此外,當用戶的訊號強度發生抖動時,視訊流會動態調整以提供出色的再現效果。

(圖片來源:https://www.wowza.com/blog/hls-streaming-protocol)

最初, 僅 iOS 支援 HLS。但現在 HLS 已成為專有格式,幾乎所有裝置都支援它。顧名思義,HLS(HTTP Live Streaming)協議通過標準的 HTTP Web 伺服器傳送視訊內容。這意味著你無需整合任何特殊的基礎架構即可分發 HLS 內容。

HLS 擁有以下特性:

  • HLS 將播放使用 H.264 或 HEVC / H.265 編解碼器編碼的視訊。
  • HLS 將播放使用 AAC 或 MP3 編解碼器編碼的音訊。
  • HLS 視訊流一般被切成 10 秒的片段。
  • HLS 的傳輸/封裝格式是 MPEG-2 TS。
  • HLS 支援 DRM(數字版權管理)。
  • HLS 支援各種廣告標準,例如 VAST 和 VPAID。

為什麼蘋果要提出 HLS 這個協議,其實它的主要是為了解決 RTMP 協議存在的一些問題。比如 RTMP 協議不使用標準的 HTTP 介面傳輸資料,所以在一些特殊的網路環境下可能被防火牆遮蔽掉。但是 HLS 由於使用的 HTTP 協議傳輸資料,通常情況下不會遇到被防火牆遮蔽的情況。除此之外,它也很容易通過 CDN(內容分發網路)來傳輸媒體流。

3.2 HLS 自適應位元流

HLS 是一種自適應位元率流協議。因此,HLS 流可以動態地使視訊解析度自適應每個人的網路狀況。如果你正在使用高速 WiFi,則可以在手機上流式傳輸高清視訊。但是,如果你在有限資料連線的公共汽車或地鐵上,則可以以較低的解析度觀看相同的視訊。

在開始一個流媒體會話時,客戶端會下載一個包含元資料的 Extended M3U(m3u8)Playlist 檔案,用於尋找可用的媒體流。

(圖片來源:https://www.wowza.com/blog/hls-streaming-protocol)

為了便於大家的理解,我們使用 hls.js 這個 JavaScript 實現的 HLS 客戶端,所提供的 線上示例,來看一下具體的 m3u8 檔案。

「x36xhzz.m3u8」

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2149280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=1280x720,NAME="720"
url_0/193039199_mp4_h264_aac_hd_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=246440,CODECS="mp4a.40.5,avc1.42000d",RESOLUTION=320x184,NAME="240"
url_2/193039199_mp4_h264_aac_ld_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=460560,CODECS="mp4a.40.5,avc1.420016",RESOLUTION=512x288,NAME="380"
url_4/193039199_mp4_h264_aac_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x480,NAME="480"
url_6/193039199_mp4_h264_aac_hq_7.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6221600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,NAME="1080"
url_8/193039199_mp4_h264_aac_fhd_7.m3u8

通過觀察 Master Playlist 對應的 m3u8 檔案,我們可以知道該視訊支援以下 5 種不同清晰度的視訊:

  • 1920x1080(1080P)
  • 1280x720(720P)
  • 848x480(480P)
  • 512x288
  • 320x184

而不同清晰度視訊對應的媒體播放列表,會定義在各自的 m3u8 檔案中。這裡我們以 720P 的視訊為例,來檢視其對應的 m3u8 檔案:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:11
#EXTINF:10.000,
url_462/193039199_mp4_h264_aac_hd_7.ts
#EXTINF:10.000,
url_463/193039199_mp4_h264_aac_hd_7.ts
#EXTINF:10.000,
url_464/193039199_mp4_h264_aac_hd_7.ts
#EXTINF:10.000,
...
url_525/193039199_mp4_h264_aac_hd_7.ts
#EXT-X-ENDLIST

當用戶選定某種清晰度的視訊之後,將會下載該清晰度對應的媒體播放列表(m3u8 檔案),該列表中就會列出每個片段的資訊。HLS 的傳輸/封裝格式是 MPEG-2 TS(MPEG-2 Transport Stream),是一種傳輸和儲存包含視訊、音訊與通訊協議各種資料的標準格式,用於數字電視廣播系統,如 DVB、ATSC、IPTV 等等。

「需要注意的是利用一些現成的工具,我們是可以把多個 TS 檔案合併為 mp4 格式的視訊檔案。」如果要做視訊版權保護,那我們可以考慮使用對稱加密演算法,比如 AES-128 對切片進行對稱加密。當客戶端進行播放時,先根據 m3u8 檔案中配置的金鑰伺服器地址,獲取對稱加密的金鑰,然後再下載分片,當分片下載完成後再使用匹配的對稱加密演算法進行解密播放。

對上述過程感興趣的小夥伴可以參考 Github 上 video-hls-encrypt 這個專案,該專案深入淺出介紹了基於 HLS 流媒體協議視訊加密的解決方案並提供了完整的示例程式碼。

(圖片來源:https://github.com/hauk0101/video-hls-encrypt)

介紹完蘋果公司推出的 HLS (HTTP Live Streaming)技術,接下來我們來介紹另一種基於 HTTP 的動態自適應流 —— DASH。

四、DASH

4.1 DASH 簡介

「基於 HTTP 的動態自適應流(英語:Dynamic Adaptive Streaming over HTTP,縮寫 DASH,也稱 MPEG-DASH)是一種自適應位元率流技術,使高質量流媒體可以通過傳統的 HTTP 網路伺服器以網際網路傳遞。」類似蘋果公司的 HTTP Live Streaming(HLS)方案,MPEG-DASH 會將內容分解成一系列小型的基於 HTTP 的檔案片段,每個片段包含很短長度的可播放內容,而內容總長度可能長達數小時。

內容將被製成多種位元率的備選片段,以提供多種位元率的版本供選用。當內容被 MPEG-DASH 客戶端回放時,客戶端將根據當前網路條件自動選擇下載和播放哪一個備選方案。客戶端將選擇可及時下載的最高位元率片段進行播放,從而避免播放卡頓或重新緩衝事件。也因如此,MPEG-DASH 客戶端可以無縫適應不斷變化的網路條件並提供高質量的播放體驗,擁有更少的卡頓與重新緩衝發生率。

MPEG-DASH 是首個基於 HTTP 的自適應位元率流解決方案,它也是一項國際標準。MPEG-DASH 不應該與傳輸協議混淆 —— MPEG-DASH 使用 TCP 傳輸協議。「不同於 HLS、HDS 和 Smooth Streaming,DASH 不關心編解碼器,因此它可以接受任何編碼格式編碼的內容,如 H.265、H.264、VP9 等。」

雖然 HTML5 不直接支援 MPEG-DASH,但是已有一些 MPEG-DASH 的 JavaScript 實現允許在網頁瀏覽器中通過 HTML5 Media Source Extensions(MSE)使用 MPEG-DASH。另有其他 JavaScript 實現,如 bitdash 播放器支援使用 HTML5 加密媒體擴充套件播放有 DRM 的MPEG-DASH。當與 WebGL 結合使用,MPEG-DASH 基於 HTML5 的自適應位元率流還可實現 360° 視訊的實時和按需的高效流式傳輸。

4.2 DASH 重要概念

  • MPD:媒體檔案的描述檔案(manifest),作用類似 HLS 的 m3u8 檔案。
  • Representation:對應一個可選擇的輸出(alternative)。如 480p 視訊,720p 視訊,44100 取樣音訊等都使用 Representation 描述。
  • Segment(分片):每個 Representation 會劃分為多個 Segment。Segment 分為 4 類,其中,最重要的是:Initialization Segment(每個 Representation 都包含 1 個 Init Segment),Media Segment(每個 Representation 的媒體內容包含若干 Media Segment)。

(圖片來源:https://blog.csdn.net/yue_huang/article/details/78466537)

在國內 Bilibili 於 2018 年開始使用 DASH 技術,至於為什麼選擇 DASH 技術。感興趣的小夥伴可以閱讀 我們為什麼使用DASH 這篇文章。

講了那麼多,相信有些小夥伴會好奇 MPD 檔案長什麼樣?這裡我們來看一下西瓜視訊播放器 DASH 示例中的 MPD 檔案:

<?xmlversion="1.0"?>
<!--MPDfileGeneratedwithGPACversion0.7.2-DEV-rev559-g61a50f45-masterat2018-06-11T11:40:23.972Z-->
<MPDxmlns="urn:mpeg:dash:schema:mpd:2011"minBufferTime="PT1.500S"type="static"mediaPresentationDuration="PT0H1M30.080S"maxSegmentDuration="PT0H0M1.000S"profiles="urn:mpeg:dash:profile:full:2011">
<ProgramInformationmoreInformationURL="http://gpac.io">
<Title>xgplayer-demo_dash.mpdgeneratedbyGPAC</Title>
</ProgramInformation>

<Periodduration="PT0H1M30.080S">
<AdaptationSetsegmentAlignment="true"maxWidth="1280"maxHeight="720"maxFrameRate="25"par="16:9"lang="eng">
<ContentComponentid="1"contentType="audio"/>
<ContentComponentid="2"contentType="video"/>
<Representationid="1"mimeType="video/mp4"codecs="mp4a.40.2,avc3.4D4020"width="1280"height="720"frameRate="25"sar="1:1"startWithSAP="0"bandwidth="6046495">
<AudioChannelConfigurationschemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"value="2"/>
<BaseURL>xgplayer-demo_dashinit.mp4</BaseURL>
<SegmentListtimescale="1000"duration="1000">
<Initializationrange="0-1256"/>
<SegmentURLmediaRange="1257-1006330"indexRange="1257-1300"/>
<SegmentURLmediaRange="1006331-1909476"indexRange="1006331-1006374"/>
...
<SegmentURLmediaRange="68082016-68083543"indexRange="68082016-68082059"/>
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>

(檔案來源:https://h5player.bytedance.com/examples/)

在播放視訊時,西瓜視訊播放器會根據 MPD 檔案,自動請求對應的分片進行播放。

前面我們已經提到了 Bilibili,接下來不得不提其開源的一個著名的開源專案 —— flv.js,不過在介紹它之前我們需要來了解一下 FLV 流媒體格式。

五、FLV

5.1 FLV 檔案結構

FLV 是 FLASH Video 的簡稱,FLV 流媒體格式是隨著 Flash MX 的推出發展而來的視訊格式。由於它形成的檔案極小、載入速度極快,使得網路觀看視訊檔案成為可能,它的出現有效地解決了視訊檔案匯入 Flash 後,使匯出的 SWF 檔案體積龐大,不能在網路上很好的使用等問題。

FLV 檔案由 FLV Header 和 FLV Body 兩部分構成,而 FLV Body 由一系列的 Tag 構成:

5.1.1 FLV 標頭檔案

FLV 標頭檔案:(9 位元組)

  • 1-3:前 3 個位元組是檔案格式標識(FLV 0x46 0x4C 0x56)。
  • 4-4:第 4 個位元組是版本(0x01)。
  • 5-5:第 5 個位元組的前 5 個 bit 是保留的必須是 0。
    • 第 5 個位元組的第 6 個 bit 音訊型別標誌(TypeFlagsAudio)。
    • 第 5 個位元組的第 7 個 bit 也是保留的必須是 0。
    • 第5個位元組的第8個bit視訊型別標誌(TypeFlagsVideo)。
  • 6-9: 第 6-9 的四個位元組還是保留的,其資料為 00000009。
  • 整個檔案頭的長度,一般是 9(3+1+1+4)。
5.1.2 tag 基本格式

tag 型別資訊,固定長度為 15 位元組:

  • 1-4:前一個 tag 長度(4位元組),第一個 tag 就是 0。
  • 5-5:tag 型別(1 位元組);0x8 音訊;0x9 視訊;0x12 指令碼資料。
  • 6-8:tag 內容大小(3 位元組)。
  • 9-11:時間戳(3 位元組,毫秒)(第 1 個 tag 的時候總是為 0,如果是指令碼 tag 就是 0)。
  • 12-12:時間戳擴充套件(1 位元組)讓時間戳變成 4 位元組(以儲存更長時間的 flv 時間資訊),本位元組作為時間戳的最高位。

在 flv 回放過程中,播放順序是按照 tag 的時間戳順序播放。任何加入到檔案中時間設定資料格式都將被忽略。

  • 13-15:streamID(3 位元組)總是 0。

FLV 格式詳細的結構圖如下圖所示:

在瀏覽器中 HTML5 的<video>是不支援直接播放 FLV 視訊格式,需要藉助 flv.js 這個開源庫來實現播放 FLV 視訊格式的功能。

5.2 flv.js 簡介

flv.js 是用純 JavaScript 編寫的 HTML5 Flash Video(FLV)播放器,它底層依賴於 Media Source Extensions。在實際執行過程中,它會自動解析 FLV 格式檔案並餵給原生 HTML5 Video 標籤播放音視訊資料,使瀏覽器在不借助 Flash 的情況下播放 FLV 成為可能。

5.2.1 flv.js 的特性
  • 支援播放 H.264 + AAC / MP3 編碼的 FLV 檔案;
  • 支援播放多段分段視訊;
  • 支援播放 HTTP FLV 低延遲實時流;
  • 支援播放基於 WebSocket 傳輸的 FLV 實時流;
  • 相容 Chrome,FireFox,Safari 10,IE11 和 Edge;
  • 極低的開銷,支援瀏覽器的硬體加速。
5.2.2 flv.js 的限制
  • MP3 音訊編解碼器無法在 IE11/Edge 上執行;
  • HTTP FLV 直播流不支援所有的瀏覽器。
5.2.3 flv.js 的使用
<scriptsrc="flv.min.js"></script>
<videoid="videoElement"></video>
<script>
if(flvjs.isSupported()){
varvideoElement=document.getElementById('videoElement');
varflvPlayer=flvjs.createPlayer({
type:'flv',
url:'http://example.com/flv/video.flv'
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script>

5.3 flv.js 工作原理

flv.js 的工作原理是將 FLV 檔案流轉換為 ISO BMFF(Fragmented MP4)片段,然後通過 Media Source Extensions API 將 mp4 段餵給 HTML5<video>元素。flv.js 的設計架構圖如下圖所示:

(圖片來源:https://github.com/bilibili/flv.js/blob/master/docs/design.md)

有關 flv.js 工作原理更詳細的介紹,感興趣的小夥們可以閱讀 花椒開源專案實時互動流媒體播放器 這篇文章。現在我們已經介紹了 hls.js 和 flv.js 這兩個主流的流媒體解決方案,其實它們的成功離不開 Media Source Extensions 這個幕後英雄默默地支援。因此,接下來阿寶哥將帶大家一起認識一下 MSE(Media Source Extensions)。

六、MSE

6.1 MSE API

媒體源擴充套件 API(Media Source Extensions) 提供了實現無外掛且基於 Web 的流媒體的功能。使用 MSE,媒體串流能夠通過 JavaScript 建立,並且能通過使用audiovideo元素進行播放。

近幾年來,我們已經可以在 Web 應用程式上無外掛地播放視訊和音訊了。但是,現有架構過於簡單,只能滿足一次播放整個曲目的需要,無法實現拆分/合併數個緩衝檔案。早期的流媒體主要使用 Flash 進行服務,以及通過 RTMP 協議進行視訊串流的 Flash 媒體伺服器。

媒體源擴充套件(MSE)實現後,情況就不一樣了。MSE 使我們可以把通常的單個媒體檔案的src值替換成引用MediaSource物件(一個包含即將播放的媒體檔案的準備狀態等資訊的容器),以及引用多個SourceBuffer物件(代表多個組成整個串流的不同媒體塊)的元素。

為了便於大家理解,我們來看一下基礎的 MSE 資料流:

MSE 讓我們能夠根據內容獲取的大小和頻率,或是記憶體佔用詳情(例如什麼時候快取被回收),進行更加精準地控制。它是基於它可擴充套件的 API 建立自適應位元率流客戶端(例如 DASH 或 HLS 的客戶端)的基礎。

在現代瀏覽器中創造能相容 MSE 的媒體非常費時費力,還要消耗大量計算機資源和能源。此外,還須使用外部應用程式將內容轉換成合適的格式。雖然瀏覽器支援相容 MSE 的各種媒體容器,但採用 H.264 視訊編碼、AAC 音訊編碼和 MP4 容器的格式是非常常見的,所以 MSE 需要相容這些主流的格式。此外 MSE 還為開發者提供了一個 API,用於執行時檢測容器和編解碼是否受支援。

6.2 MediaSource 介面

MediaSource 是 Media Source Extensions API 表示媒體資源 HTMLMediaElement 物件的介面。MediaSource 物件可以附著在 HTMLMediaElement 在客戶端進行播放。在介紹 MediaSource 介面前,我們先來看一下它的結構圖:

(圖片來源 —— https://www.w3.org/TR/media-source/)

要理解 MediaSource 的結構圖,我們得先來介紹一下客戶端音視訊播放器播放一個視訊流的主要流程:

獲取流媒體 -> 解協議 -> 解封裝 -> 音、視訊解碼 -> 音訊播放及視訊渲染(需處理音視訊同步)。

由於採集的原始音視訊資料比較大,為了方便網路傳輸,我們通常會使用編碼器,如常見的 H.264 或 AAC 來壓縮原始媒體訊號。最常見的媒體訊號是視訊,音訊和字幕。比如,日常生活中的電影,就是由不同的媒體訊號組成,除運動圖片外,大多數電影還含有音訊和字幕。

常見的視訊編解碼器有:H.264,HEVC,VP9 和 AV1。而音訊編解碼器有:AAC,MP3 或 Opus。每個媒體訊號都有許多不同的編解碼器。下面我們以西瓜視訊播放器的 Demo 為例,來直觀感受一下音訊軌、視訊軌和字幕軌:

現在我們來開始介紹 MediaSource 介面的相關內容。

6.2.1 狀態
enumReadyState{
"closed",//指示當前源未附加到媒體元素。
"open",//源已經被媒體元素開啟,資料即將被新增到SourceBuffer物件中
"ended"//源仍附加到媒體元素,但endOfStream()已被呼叫。
};
6.2.2 流終止異常
enumEndOfStreamError{
"network",//終止播放併發出網路錯誤訊號。
"decode"//終止播放併發出解碼錯誤訊號。
};
6.2.3 構造器
[Constructor]
interfaceMediaSource:EventTarget{
readonlyattributeSourceBufferListsourceBuffers;
readonlyattributeSourceBufferListactiveSourceBuffers;
readonlyattributeReadyStatereadyState;
attributeunrestricteddoubleduration;
attributeEventHandleronsourceopen;
attributeEventHandleronsourceended;
attributeEventHandleronsourceclose;

SourceBufferaddSourceBuffer(DOMStringtype);
voidremoveSourceBuffer(SourceBuffersourceBuffer);
voidendOfStream(optionalEndOfStreamErrorerror);
voidsetLiveSeekableRange(doublestart,doubleend);
voidclearLiveSeekableRange();
staticbooleanisTypeSupported(DOMStringtype);
};
6.2.4 屬性
  • MediaSource.sourceBuffers—— 只讀:返回一個 SourceBufferList 物件,包含了這個 MediaSource 的SourceBuffer 的物件列表。
  • MediaSource.activeSourceBuffers—— 只讀:返回一個 SourceBufferList 物件,包含了這個MediaSource.sourceBuffers 中的 SourceBuffer 子集的物件—即提供當前被選中的視訊軌(video track),啟用的音訊軌(audio tracks)以及顯示/隱藏的字幕軌(text tracks)的物件列表
  • MediaSource.readyState—— 只讀:返回一個包含當前 MediaSource 狀態的集合,即使它當前沒有附著到一個 media 元素(closed),或者已附著並準備接收 SourceBuffer 物件(open),亦或者已附著但這個流已被 MediaSource.endOfStream() 關閉。
  • MediaSource.duration:獲取和設定當前正在推流媒體的持續時間。
  • onsourceopen:設定 sourceopen 事件對應的事件處理程式。
  • onsourceended:設定 sourceended 事件對應的事件處理程式。
  • onsourceclose:設定 sourceclose 事件對應的事件處理程式。
6.2.5 方法
  • MediaSource.addSourceBuffer():建立一個帶有給定 MIME 型別的新的 SourceBuffer 並新增到 MediaSource 的 SourceBuffers 列表。
  • MediaSource.removeSourceBuffer():刪除指定的 SourceBuffer 從這個 MediaSource 物件中的 SourceBuffers 列表。
  • MediaSource.endOfStream():表示流的結束。
6.2.6 靜態方法
  • MediaSource.isTypeSupported():返回一個 Boolean 值表明給定的 MIME 型別是否被當前的瀏覽器支援—— 這意味著是否可以成功的建立這個 MIME 型別的 SourceBuffer 物件。
6.2.7 使用示例
varvidElement=document.querySelector('video');

if(window.MediaSource){//(1)
varmediaSource=newMediaSource();
vidElement.src=URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen',sourceOpen);
}else{
console.log("TheMediaSourceExtensionsAPIisnotsupported.")
}

functionsourceOpen(e){
URL.revokeObjectURL(vidElement.src);
varmime='video/mp4;codecs="avc1.42E01E,mp4a.40.2"';
varmediaSource=e.target;
varsourceBuffer=mediaSource.addSourceBuffer(mime);//(2)
varvideoUrl='hello-mse.mp4';
fetch(videoUrl)//(3)
.then(function(response){
returnresponse.arrayBuffer();
})
.then(function(arrayBuffer){
sourceBuffer.addEventListener('updateend',function(e){(4)
if(!sourceBuffer.updating&&mediaSource.readyState==='open'){
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);//(5)
});
}

以上示例介紹瞭如何使用 MSE API,接下來我們來分析一下主要的工作流程:

  • (1) 判斷當前平臺是否支援 Media Source Extensions API,若支援的話,則建立 MediaSource 物件,且繫結 sourceopen 事件處理函式。
  • (2) 建立一個帶有給定 MIME 型別的新的 SourceBuffer 並新增到 MediaSource 的 SourceBuffers 列表。
  • (3) 從遠端流伺服器下載視訊流,並轉換成 ArrayBuffer 物件。
  • (4) 為 sourceBuffer 物件新增 updateend 事件處理函式,在視訊流傳輸完成後關閉流。
  • (5) 往 sourceBuffer 物件中新增已轉換的 ArrayBuffer 格式的視訊流資料。

上面阿寶哥只是簡單介紹了一下 MSE API,想深入瞭解它實際應用的小夥伴,可以進一步瞭解一下「hls.js」「flv.js」專案。接下來阿寶哥將介紹音視訊基礎之多媒體容器格式。

七、多媒體封裝格式

一般情況下,一個完整的視訊檔案是由音訊和視訊兩部分組成的。常見的 AVI、RMVB、MKV、ASF、WMV、MP4、3GP、FLV 等檔案只能算是一種封裝格式。H.264,HEVC,VP9 和 AV1 等就是視訊編碼格式,MP3、AAC 和 Opus 等就是音訊編碼格式。「比如:將一個 H.264 視訊編碼檔案和一個 AAC 音訊編碼檔案按 MP4 封裝標準封裝以後,就得到一個 MP4 字尾的視訊檔案,也就是我們常見的 MP4 視訊檔案了。」

音視訊編碼的主要目的是壓縮原始資料的體積,而封裝格式(也稱為多媒體容器),比如 MP4,MKV,是用來儲存/傳輸編碼資料,並按一定規則把音視訊、字幕等資料組織起來,同時還會包含一些元資訊,比如當前流中包含哪些編碼型別、時間戳等,播放器可以按照這些資訊來匹配解碼器、同步音視訊。

為了能更好地理解多媒體封裝格式,我們再來回顧一下視訊播放器的原理。

7.1 視訊播放器原理

視訊播放器是指能播放以數字訊號形式儲存的視訊的軟體,也指具有播放視訊功能的電子器件產品。大多數視訊播放器(除了少數波形檔案外)攜帶解碼器以還原經過壓縮的媒體檔案,視訊播放器還要內建一整套轉換頻率以及緩衝的演算法。大多數的視訊播放器還能支援播放音訊檔案。

視訊播放基本處理流程大致包括以下幾個階段:

(1)解協議

從原始的流媒體協議資料中刪除信令資料,只保留音視訊資料,如採用 RTMP 協議傳輸的資料,經過解協議後輸出 flv 格式的資料。

(2)解封裝

分離音訊和視訊壓縮編碼資料,常見的封裝格式 MP4,MKV,RMVB,FLV,AVI 這些格式。從而將已經壓縮編碼的視訊、音訊資料放到一起。例如 FLV 格式的資料經過解封裝後輸出 H.264 編碼的視訊碼流和 AAC 編碼的音訊碼流。

(3)解碼

視訊,音訊壓縮編碼資料,還原成非壓縮的視訊,音訊原始資料,音訊的壓縮編碼標準包括 AAC,MP3,AC-3 等,視訊壓縮編碼標準包含 H.264,MPEG2,VC-1 等經過解碼得到非壓縮的視訊顏色資料如 YUV420P,RGB 和非壓縮的音訊資料如 PCM 等。

(4)音視訊同步

將同步解碼出來的音訊和視訊資料分別送至系統音效卡和顯示卡播放。

瞭解完視訊播放器的原理,下一步我們來介紹多媒體封裝格式。

7.2 多媒體封裝格式

對於數字媒體資料來說,容器就是一個可以將多媒體資料混在一起存放的東西,就像是一個包裝箱,它可以對音、視訊資料進行打包裝箱,將原來的兩塊獨立的媒體資料整合到一起,當然也可以單單隻存放一種型別的媒體資料。

「有時候,多媒體容器也稱封裝格式,它只是為編碼後的多媒體資料提供了一個 “外殼”,也就是將所有的處理好的音訊、視訊或字幕都包裝到一個檔案容器內呈現給觀眾,這個包裝的過程就叫封裝。」常用的封裝格式有:MP4,MOV,TS,FLV,MKV 等。這裡我們來介紹大家比較熟悉的 MP4 封裝格式。

7.2.1 MP4 封裝格式

MPEG-4 Part 14(MP4)是最常用的容器格式之一,通常以 .mp4 檔案結尾。它用於 HTTP(DASH)上的動態自適應流,也可以用於 Apple 的 HLS 流。MP4 基於 ISO 基本媒體檔案格式(MPEG-4 Part 12),該格式基於 QuickTime 檔案格式。MPEG 代表動態影象專家組,是國際標準化組織(ISO)和國際電工委員會(IEC)的合作。MPEG 的成立是為了設定音訊和視訊壓縮與傳輸的標準。

MP4 支援多種編解碼器,常用的視訊編解碼器是 H.264 和 HEVC,而常用的音訊編解碼器是 AAC,AAC 是著名的 MP3 音訊編解碼器的後繼產品。

MP4 是由一些列的 box 組成,它的最小組成單元是 box。MP4 檔案中的所有資料都裝在 box 中,即 MP4 檔案由若干個 box 組成,每個 box 有型別和長度,可以將 box 理解為一個數據物件塊。box 中可以包含另一個 box,這種 box 稱為 container box。

一個 MP4 檔案首先會有且僅有 一個ftype型別的 box,作為 MP4 格式的標誌幷包含關於檔案的一些資訊,之後會有且只有一個moov型別的 box(movie box),它是一種 container box,可以有多個,也可以沒有,媒體資料的結構由 metadata 進行描述。

相信有些讀者會有疑問 —— 實際的 MP4 檔案結構是怎麼樣的?通過使用 mp4box.js 提供的線上服務,我們可以方便的檢視本地或線上 MP4 檔案內部的結構:

mp4box.js 線上地址:https://gpac.github.io/mp4box.js/test/filereader.html

由於 MP4 檔案結構比較複雜(不信請看下圖),這裡我們就不繼續展開,有興趣的讀者,可以自行閱讀相關文章。

接下來,我們來介紹 Fragmented MP4 容器格式。

7.2.2 Fragmented MP4 封裝格式

MP4 ISO Base Media 檔案格式標準允許以 fragmented 方式組織 box,這也就意味著 MP4 檔案可以組織成這樣的結構,由一系列的短的 metadata/data box 對組成,而不是一個長的 metadata/data 對。Fragmented MP4 檔案結構如下圖所示,圖中只包含了兩個 fragments:

(圖片來源 —— https://alexzambelli.com/blog/2009/02/10/smooth-streaming-architecture/)

在 Fragmented MP4 檔案中含有三個非常關鍵的 boxes:moovmoofmdat

  • moov(movie metadata box):用於存放多媒體 file-level 的元資訊。
  • mdat(media data box):和普通 MP4 檔案的mdat一樣,用於存放媒體資料,不同的是普通 MP4 檔案只有一個mdatbox,而 Fragmented MP4 檔案中,每個 fragment 都會有一個mdat型別的 box。
  • moof(movie fragment box):用於存放 fragment-level 的元資訊。該型別的 box 在普通的 MP4 檔案中是不存在的,而在 Fragmented MP4 檔案中,每個 fragment 都會有一個moof型別的 box。

Fragmented MP4 檔案中的 fragment 由moofmdat兩部分組成,每個 fragment 可以包含一個音訊軌或視訊軌,並且也會包含足夠的元資訊,以保證這部分資料可以單獨解碼。Fragment 的結構如下圖所示:

(圖片來源 —— https://alexzambelli.com/blog/2009/02/10/smooth-streaming-architecture/)

同樣,利用 mp4box.js 提供的線上服務,我們也可以清晰的檢視 Fragmented MP4 檔案的內部結構:

我們已經介紹了 MP4 和 Fragmented MP4 這兩種容器格式,我們用一張圖來總結一下它們之間的主要區別:

八、阿寶哥有話說

8.1 如何實現視訊本地預覽

視訊本地預覽的功能主要利用URL.createObjectURL()方法來實現。URL.createObjectURL() 靜態方法會建立一個 DOMString,其中包含一個表示引數中給出的物件的 URL。這個 URL 的生命週期和建立它的視窗中的 document 繫結。這個新的 URL 物件表示指定的 File 物件或 Blob 物件。

<!DOCTYPEhtml>
<html>
<head>
<metacharset="UTF-8"/>
<metaname="viewport"content="width=device-width,initial-scale=1.0"/>
<title>視訊本地預覽示例</title>
</head>
<body>
<h3>阿寶哥:視訊本地預覽示例</h3>
<inputtype="file"accept="video/*"onchange="loadFile(event)"/>
<video
id="previewContainer"
controls
width="480"
height="270"
style="display:none;"
></video>

<script>
constloadFile=function(event){
constreader=newFileReader();
reader.onload=function(){
constoutput=document.querySelector("#previewContainer");
output.style.display="block";
output.src=URL.createObjectURL(newBlob([reader.result]));
};
reader.readAsArrayBuffer(event.target.files[0]);
};
</script>
</body>
</html>

8.2 如何實現播放器截圖

播放器截圖功能主要利用CanvasRenderingContext2D.drawImage()API 來實現。Canvas 2D API 中的 CanvasRenderingContext2D.drawImage() 方法提供了多種方式在 Canvas 上繪製圖像。

drawImage API 的語法如下:

void ctx.drawImage(image, dx, dy);

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

其中 image 引數表示繪製到上下文的元素。允許任何的 canvas 影象源(CanvasImageSource),例如:CSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap 或者 OffscreenCanvas。

<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="UTF-8"/>
<metaname="viewport"content="width=device-width,initial-scale=1.0"/>
<title>播放器截圖示例</title>
</head>
<body>
<h3>阿寶哥:播放器截圖示例</h3>
<videoid="video"controls="controls"width="460"height="270"crossorigin="anonymous">
<!--請替換為實際視訊地址-->
<sourcesrc="https://xxx.com/vid_159411468092581"/>
</video>
<buttononclick="captureVideo()">截圖</button>
<script>
letvideo=document.querySelector("#video");
letcanvas=document.createElement("canvas");
letimg=document.createElement("img");
img.crossOrigin="";
letctx=canvas.getContext("2d");

functioncaptureVideo(){
canvas.width=video.videoWidth;
canvas.height=video.videoHeight;
ctx.drawImage(video,0,0,canvas.width,canvas.height);
img.src=canvas.toDataURL();
document.body.append(img);
}
</script>
</body>
</html>

現在我們已經知道如何獲取視訊的每一幀,其實在結合 gif.js 這個庫提供的 GIF 編碼功能,我們就可以快速地實現擷取視訊幀生成 GIF 動畫的功能。這裡阿寶哥不繼續展開介紹,有興趣的小夥伴可以閱讀 ”使用 JS 直接擷取 視訊片段 生成 gif 動畫“ 這篇文章。

8.3 如何實現 Canvas 播放視訊

使用 Canvas 播放視訊主要是利用ctx.drawImage(video, x, y, width, height)來對視訊當前幀的影象進行繪製,其中 video 引數就是頁面中的 video 物件。所以如果我們按照特定的頻率不斷獲取 video 當前畫面,並渲染到 Canvas 畫布上,就可以實現使用 Canvas 播放視訊的功能。

<!DOCTYPEhtml>
<html>
<head>
<metacharset="UTF-8"/>
<metaname="viewport"content="width=device-width,initial-scale=1.0"/>
<title>使用Canvas播放視訊</title>
</head>
<body>
<h3>阿寶哥:使用 Canvas 播放視訊</h3>
<videoid="video"controls="controls"style="display:none;">
<!--請替換為實際視訊地址-->
<sourcesrc="https://xxx.com/vid_159411468092581"/>
</video>
<canvas
id="myCanvas"
width="460"
height="270"
style="border:1pxsolidblue;"
></canvas>
<div>
<buttonid="playBtn">播放</button>
<buttonid="pauseBtn">暫停</button>
</div>
<script>
constvideo=document.querySelector("#video");
constcanvas=document.querySelector("#myCanvas");
constplayBtn=document.querySelector("#playBtn");
constpauseBtn=document.querySelector("#pauseBtn");
constcontext=canvas.getContext("2d");
lettimerId=null;

functiondraw(){
if(video.paused||video.ended)return;
context.clearRect(0,0,canvas.width,canvas.height);
context.drawImage(video,0,0,canvas.width,canvas.height);
timerId=setTimeout(draw,0);
}

playBtn.addEventListener("click",()=>{
if(!video.paused)return;
video.play();
draw();
});

pauseBtn.addEventListener("click",()=>{
if(video.paused)return;
video.pause();
clearTimeout(timerId);
});
</script>
</body>
</html>

8.4 如何實現色度鍵控(綠屏效果)

上一個示例我們介紹了使用 Canvas 播放視訊,那麼可能有一些小夥伴會有疑問,為什麼要通過 Canvas 繪製視訊,Video 標籤不 “香” 麼?這是因為 Canvas 提供了getImageDataputImageData方法使得開發者可以動態地更改每一幀影象的顯示內容。這樣的話,我們就可以實時地操縱視訊資料來合成各種視覺特效到正在呈現的視訊畫面中。

比如 MDN 上的 ”使用 canvas 處理視訊“ 的教程中就演示瞭如何使用 JavaScript 程式碼執行色度鍵控(綠屏或藍屏效果)。

所謂的色度鍵控,又稱色彩嵌空,是一種去背合成技術。Chroma 為純色之意,Key 則是抽離顏色之意。把被拍攝的人物或物體放置於綠幕的前面,並進行去背後,將其替換成其他的背景。此技術在電影、電視劇及遊戲製作中被大量使用,色鍵也是虛擬攝影棚(Virtual studio)與視覺效果(Visual effects)當中的一個重要環節。

下面我們來看一下關鍵程式碼:

processor.computeFrame=functioncomputeFrame(){
this.ctx1.drawImage(this.video,0,0,this.width,this.height);
letframe=this.ctx1.getImageData(0,0,this.width,this.height);
letl=frame.data.length/4;

for(leti=0;i<l;i++){
letr=frame.data[i*4+0];
letg=frame.data[i*4+1];
letb=frame.data[i*4+2];
if(g>100&&r>100&&b<43)
frame.data[i*4+3]=0;
}
this.ctx2.putImageData(frame,0,0);
return;
}

以上的computeFrame()方法負責獲取一幀資料並執行色度鍵控效果。利用色度鍵控技術,我們還可以實現純客戶端實時蒙版彈幕。這裡阿寶哥就不詳細介紹了,感興趣的小夥伴可以閱讀一下創宇前端 ”彈幕不擋人!基於色鍵技術的純客戶端實時蒙版彈幕“ 這篇文章。

九、參考資源

  • Baike - 流媒體
  • MDN - Video_and_audio_content
  • MDN - Range_requests
  • MDN - Media_Source_Extensions_API
  • Wiki - MPEG-DASH
  • w3.org - Media Source Extensions